/* sender.c -- send advrp routing updates
 *
 *    Copyright 2010 Patrick Allen
 *
 *    This program is free software: you can redistribute it and/or modify
 *    it under the terms of the GNU General Public License as published by
 *    the Free Software Foundation, either version 3 of the License, or
 *    (at your option) any later version.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU General Public License for more details.
 *
 *    You should have received a copy of the GNU General Public License
 *    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
*/

#include <stdio.h>
#include <netinet/in.h>
#include <errno.h>
#include <signal.h>

#include "advrp.h"
#include "debug.h"
#include "sender.h"

extern pthread_mutex_t mutexAdvrp;

extern int UPDATE_TIMER;
extern interface * firstInterface;
extern internalRoute * firstInternalRoute;
extern pthread_cond_t condCreate;
extern pthread_mutex_t mutexCreate;

void add_update_record(internalRoute * iroute, updateRecord * rec);
void setup_network(int * fd, struct sockaddr_in * server, 
                   struct sockaddr_in * client);
updateMessage * prepare_msg(updateMessage * msg, interface * cif);
void add_update_record(internalRoute * route, updateRecord * rec);
void send_msg(updateMessage * msg, interface * cif, int fd, 
              struct sockaddr_in * server, struct sockaddr_in * client);

void * sender_loop(void * notused)
{
	updateMessage msg;
	interface * cif;             /* current interface */
	int counter = 0;
	int index, size, fd;
	struct sockaddr_in server, client;
	sigset_t newmask;

	setup_network(&fd, &server, &client);

	/* Drop effective privs */
	drop_privs(1);

	/* Ignore all signals */
	newmask.__val[0] = SIGINT;
	newmask.__val[1] = SIGTERM;
	newmask.__val[2] = SIGHUP;
	newmask.__val[3] = SIGALRM;
	pthread_sigmask(SIG_SETMASK, &newmask, 0);

	DBG(DEV_1, "Sender thread id: %u", pthread_self());

	/* Tell main to continue creating threads */
	pthread_mutex_lock(&mutexCreate);
	pthread_cond_signal(&condCreate);
	pthread_mutex_unlock(&mutexCreate);

	msg.version = htonl(ADVRP_VERSION);

	while (1) {
		pthread_mutex_lock(&mutexAdvrp);

		/* Loop through each interface */
		for (cif = firstInterface; cif != NULL; cif = cif->next) {
			if (! cif->active) {
				DBG(SEND_1, "Interface %s not active.",
			            cif->ifname);
				continue;
			}

			server.sin_addr.s_addr = cif->broadcast;
			prepare_msg(&msg, cif);

			/* Don't send blank messages */
			if (! msg.nrec)
				continue;

			send_msg(&msg, cif, fd, &server, &client);
		}

		pthread_mutex_unlock(&mutexAdvrp);
		DBG(SEND_1, "Sleeping for %u seconds.", UPDATE_TIMER);
		sleep(UPDATE_TIMER);
	}

	/* Never reached */
}

void setup_network(int * fd, struct sockaddr_in * server, 
                   struct sockaddr_in * client)
{
	int ret;
	int on = 1;

	if ((*fd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
		DBG(ERR, "Could not create socket.");
		cleanup();
	} else {
		DBG(DEV_1, "Created socket %d.", *fd);
	}

	/* "socket" option means "layer 3" option */

	ret = setsockopt(*fd, SOL_SOCKET, SO_BROADCAST, &on, sizeof(int));

	if (ret == -1) {
		DBG(ERR, "Could not set socket options.");
		cleanup();
	} else {
		DBG(DEV_2, "Successfully set socket options.");
	}

	client->sin_family = AF_INET;
	client->sin_addr.s_addr = htonl(INADDR_ANY);
	client->sin_port = htons(INADDR_ANY);
	ret = bind(*fd, (struct sockaddr *) client, sizeof(*client));
	if (ret) {
		DBG(ERR, "Could not bind.");
		close(*fd);
		cleanup();
	} else {
		DBG(SEND_2, "Call to bind was successful.");
	}

	server->sin_family = AF_INET;
	server->sin_port = htons(ADVRP_PORT);
	return;
}

updateMessage * prepare_msg(updateMessage * msg, interface * cif)
{
	int index = 0;
	internalRoute * route;

	DBG(SEND_1, "Preparing message for interface %s.", cif->ifname);
	msg->nrec = 0;

	/* Find all routes to be advertised on this interface */
	for (route = firstInternalRoute; route != NULL; route = route->next) {
		if (route->neigh->iface == cif &&
		    SPLIT_HORIZON == ON) {
			DBG(SEND_2, "Split horizon prevents sending route: "
			    "%s/%d via %s on interface %s.",
			    route->route->netstr, route->route->preflen,
			    route->neigh->ipstr, cif->ifname);
			continue;
		}

		add_update_record(route, &msg->updateRecord[index]);
		index++;
		msg->nrec++;
	}
	return msg;
}

void add_update_record(internalRoute * route, updateRecord * rec)
{
	DBG(DEV_INOUT, "Entered function.");
	rec->network = route->route->network;
	DBG(DEV_2, "Added network to record...");
	rec->preflen = route->route->preflen;
	rec->metric = route->route->metric;
	DBG(SEND_3, "Adding record: %s/%d in %d", route->route->netstr,
	    rec->preflen, rec->metric);
	return;
}

void send_msg(updateMessage * msg, interface * cif, int fd, 
              struct sockaddr_in * server, struct sockaddr_in * client)
{
	int ret, size;

	/* Calculate size of the message */

	size = sizeof(updateMessage) - 
	       (sizeof(updateRecord) * (RECORDS_IN_UPDATE - msg->nrec));

	msg->nrec = htonl(msg->nrec);

	/* Send the message */

	errno = 0;
	ret = sendto(fd, msg, size, 0, (struct sockaddr *) server, 
	             sizeof(*server));
	if (ret == -1) {
		DBG(ERR, "Could not send message: %s, fd: %d.", 
		    strerror(errno), fd);
	} else {
		DBG(SEND_1, "Message sent.");
	}
}
